/****************************************************************************
                Университет:    Санкт-Петербургский Политехнический Университет
                Кафедра:        Компьютерные системы и программные технологии
                Аспирант:       Быков Илья Иванович
                Программа:      Расчет коэффициентов распределения
                Модуль:         commands.h
                Назначение:
****************************************************************************/

#include <QDebug>
#include <QMouseEvent>
#include <QPainter>
#include <QTextStream>
#include <QUndoStack>
#include <QDomElement>
#include <QDomDocument>
#include <QFrame>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QSpacerItem>

#include <QGraphicsLinearLayout>
#include <QGraphicsWidget>
#include <QGraphicsProxyWidget>
#include <QIntValidator>
#include <QFocusEvent>
#include <QCoreApplication>

#include "mainwindow.h"
#include "document.h"
#include "node.h"
#include "edge.h"
#include "addnodecommand.h"
#include "resizenodecommand.h"
#include "movenodecommand.h"
#include "addedgecommand.h"
#include "changebalcommand.h"

/******************************************************************************
** Document
*/

Document::Document(QWidget *parent)
    : QGraphicsView(parent)
    , m_currentIndex(-1)
    , m_mousePressIndex(-1)
    , m_resizeHandlePressed(false)
    , m_tmpSourceNode(0)
    , m_tmpDestNode(0)
    , m_lastNodeNumber(0)
    , m_proxyWidget(0)
    , m_bal(0)
    , m_balEdit(0)
    , m_power_precision(3)
    , m_resistance_visible(true)
{
    QGraphicsScene *scene = new QGraphicsScene(this);
    scene->setItemIndexMethod(QGraphicsScene::NoIndex);
    setScene(scene);

    m_undoStack = new QUndoStack(this);

    setAutoFillBackground(true);
    setBackgroundRole(QPalette::Base);

    QPalette pal = palette();
    pal.setBrush(QPalette::Base, QPixmap(":/icons/background.png"));
    pal.setColor(QPalette::HighlightedText, Qt::red);
    setPalette(pal);

    connect(scene, SIGNAL(selectionChanged()), MainWindow::instance(), SLOT(updateActions()));

    // Создаем виджет
    QWidget *widget = new QWidget;
    // widget->setPalette(QPalette(Qt::white));

    // Создаем метку для балансирующего узла
    QLabel *label = new QLabel("Балансирующий узел: ");
    m_balEdit = new QLineEdit;
    m_balEdit->setMaxLength(3);
    pal.setColor(QPalette::Base, Qt::yellow);
    m_balEdit->setPalette(pal);
    m_balEdit->setValidator(new QIntValidator(0, 999, m_balEdit));
    connect(m_balEdit, SIGNAL(editingFinished()), SLOT(balEditFinished()));

    QHBoxLayout *layout = new QHBoxLayout;

    layout->addWidget(label);
    layout->addWidget(m_balEdit);

    widget->setLayout(layout);
    m_proxyWidget = scene->addWidget(widget);
    m_proxyWidget->resize(220., m_proxyWidget->size().height());

    m_balEdit->installEventFilter(this);
}

Document::~Document()
{
    delete m_proxyWidget;
    disconnect(scene());

    // Удаление временных файлов
    deleteTemporaryFiles();
}

void Document::addNode(Node *node, bool update)
{
    QList<Edge*> edgeList = node->edges();
    foreach (Edge *edge, edgeList) {
        Node *n = node == edge->sourceNode() ? edge->destNode() : edge->sourceNode();
        n->addEdge(edge);
    }

    scene()->addItem(node);
    if (update)
        updateEdges();
}

void Document::removeNode(Node *node, bool update)
{
    QList<Edge*> edgeList = node->edges();
    foreach (Edge *edge, edgeList) {
        Node *n = node == edge->sourceNode() ? edge->destNode() : edge->sourceNode();
        n->removeEdge(edge);
    }
    
    scene()->removeItem(node);
    if (update)
        updateEdges();
}

void Document::addEdge(Edge *edge, bool update)
{
    scene()->addItem(edge);
    edge->sourceNode()->addEdge(edge);
    edge->destNode()->addEdge(edge);

    if (update)
        updateEdges();
}

void Document::removeEdge(Edge *edge, bool update)
{
    edge->sourceNode()->removeEdge(edge);
    edge->destNode()->removeEdge(edge);
    scene()->removeItem(edge);
    if (update)
        updateEdges();
}

QUndoStack *Document::undoStack() const
{
    return m_undoStack;
}

int Document::freeNodeNumber() const
{
    QList<Node*> nodeList = nodes();
    QList<int> numberList;

    foreach (Node *node, nodeList) { // Список номеров всех узлов
        numberList << node->number();
    }

    qSort(numberList.begin(), numberList.end()); // Сортировка по возрастанию

    int min = 1;
    for (int i = 0; i < numberList.size(); i++) { // Поиск первого свободного номера
        if (min == numberList[i]) {
            min++;
        }
        else {
            break;
        }
    }

    return min;
}

Node *Document::getNodeByNumber(int number) const
{
    Node *ret = 0;
    foreach (Node *node, nodes()) {
        if (node->number() == number) {
            ret = node;
            break;
        }
    }
    return ret;
}

void Document::updateEdges() const
{
    QList<Node*> nodeList = nodes();
    foreach (Node *node, nodeList)  // Сбросить все номера
    {
        QList<Edge*> edgeList = node->edges();
        foreach (Edge *edge, edgeList)
        {
            edge->setNumber(0);
        }
    }

    int number = 1;
    foreach (Node *node, nodeList)  // Пронумеровать заново
    {
        QList<Edge*> edgeList = node->edges();
        foreach (Edge *edge, edgeList)
        {
            if (!edge->number())    // Не трогать пронумерованную ветвь
                edge->setNumber(number++);
        }
    }
    updateCharacters();
}

void Document::updateCharacters() const
{
    bool b = true;
    int i = 1;
    foreach (Node *node, nodes())
    {
        if (node->number() != i++)
        {
            b = false;
            break;
        }
    }
    MainWindow::instance()->setEnabledCharacters(m_bal && b);
}

void Document::deleteTemporaryFiles()
{
    if (!TemporaryFile.isEmpty())
    {
        QFile file(TemporaryFile);
        file.remove();
    }

    if ( !ReportFile.isEmpty())
    {
        QFile file(ReportFile);
        file.remove();
    }
}

int Document::getPrecision()
{
    return m_power_precision;
}

void Document::setPrecision(int precision)
{
    m_power_precision = precision;

    foreach(Edge *edge, edges())
    {
        edge->setPrecision(m_power_precision);
        edge->update();
    }
}

bool Document::getResistanceState()
{
    return m_resistance_visible;
}

void Document::setResistanceState(bool state)
{
    m_resistance_visible = state;

    foreach(Edge *edge, edges())
    {
        edge->setResistanceState(m_resistance_visible);
        edge->update();
    }
}

QList<Node*> Document::nodes() const
{
    QList<QGraphicsItem*> itemList = scene()->items();
    QList<Node*> nodeList;
    foreach (QGraphicsItem *item, itemList) {
        if (item->type() == Node::Type) {
            nodeList << (Node*)item;
        }
    }
    qSort(nodeList.begin(), nodeList.end(), Node::lessThan);
    return nodeList;
}

QList<Edge*> Document::edges() const
{
    QList<QGraphicsItem*> itemList = scene()->items();
    QList<Edge*> edgeList;
    foreach (QGraphicsItem *item, itemList) {
        if (item->type() == Edge::Type) {
            edgeList << (Edge*)item;
        }
    }
    return edgeList;
}

QList<Node*> Document::selectedNodes() const
{
    QList<QGraphicsItem*> itemList = scene()->selectedItems();
    QList<Node*> nodeList;
    foreach (QGraphicsItem *item, itemList) {
        if (item->type() == Node::Type) {
            nodeList << (Node*)item;
        }
    }
    return nodeList;
}

QList<Edge*> Document::selectedEdges() const
{
    QList<QGraphicsItem*> itemList = scene()->selectedItems();
    QList<Edge*> edgeList;
    foreach (QGraphicsItem *item, itemList) {
        if (item->type() == Edge::Type) {
            edgeList << (Edge*)item;
        }
    }
    return edgeList;
}

// Загрузка
bool Document::load(QTextStream &stream)
{
    QString allContent = stream.readAll();
    
    QDomDocument doc("eec");
    QString errorMsg;
    int errorLine, errorColumn;

    if(!doc.setContent(allContent, &errorMsg, &errorLine, &errorColumn))
    {
        return false;
    }

    QDomNodeList nodeList = doc.elementsByTagName("node");
    for (int i = 0; i < nodeList.size(); i++)
    {
        QDomElement el = nodeList.item(i).toElement();
        Node *node = new Node(QColor(el.attribute("color")), el.attribute("radius").toFloat());
        node->setNumber(el.attribute("number").toInt());

        node->setstate(el.attribute("state").toInt());
        node->setVoltage(el.attribute("voltage").toDouble());
        node->setPower(el.attribute("active_power").toDouble(), el.attribute("reactive_power").toDouble());
        node->setMaxMinActivePower(el.attribute("max_pow").toDouble(), el.attribute("min_pow").toDouble());

        addNode(node, false);
        node->setPos(el.attribute("x").toFloat(), el.attribute("y").toFloat());
        bool b = el.attribute("bal").toInt();
        if (b)
        {
            setBal(node->number());
        }
    }

    for (int i = 0; i < nodeList.size(); i++)
    {
        QDomElement el = nodeList.item(i).toElement();
        Node *srcNode = getNodeByNumber(el.attribute("number").toInt());
        if (!srcNode)
            continue;
        
        QDomNodeList edgeList = el.elementsByTagName("edge");
        for (int j = 0; j < edgeList.size(); j++)
        {
            int number = edgeList.item(j).toElement().attribute("to").toInt();

            double register_x = edgeList.item(j).toElement().attribute("register_x").toDouble();
            double register_r = edgeList.item(j).toElement().attribute("register_r").toDouble();

            double power_a = edgeList.item(j).toElement().attribute("power_a").toDouble();
            double power_r = edgeList.item(j).toElement().attribute("power_r").toDouble();

            double otr_pow = edgeList.item(j).toElement().attribute("otr_pow_a").toDouble();
            double pol_pow = edgeList.item(j).toElement().attribute("pol_pow_a").toDouble();

            int state = edgeList.item(j).toElement().attribute("state").toInt();

            Node *destNode = getNodeByNumber(number);
            if (!destNode)
                continue;
            addEdge(new Edge(srcNode, destNode, state,
                             register_r, register_x,
                             power_a, power_r,
                             otr_pow, pol_pow),
                    false);
        }
    }
    updateEdges();
    updateCharacters();
    return true;
}

// Сохранение
void Document::save(QTextStream &stream)
{
    QDomDocument doc("eec");
    QDomElement root = doc.createElement("root");
    doc.appendChild(root);

    foreach (Node *node, nodes()) {        
        QDomElement nodeElement = doc.createElement("node");
        nodeElement.setAttribute("number", node->number());
        nodeElement.setAttribute("color",  node->color().name());
        nodeElement.setAttribute("radius", node->radius());
        nodeElement.setAttribute("bal", node == m_bal);
        nodeElement.setAttribute("x", node->pos().x());
        nodeElement.setAttribute("y", node->pos().y());

        double ap, rp, u, min_pow, max_pow;
        node->getPower(ap, rp);
        node->getVoltage(u);
        node->getMaxMinActivePower(max_pow, min_pow);
        nodeElement.setAttribute("state", node->getstate());
        nodeElement.setAttribute("voltage", u);
        nodeElement.setAttribute("active_power", ap);
        nodeElement.setAttribute("reactive_power", rp);

        nodeElement.setAttribute("max_pow", max_pow);
        nodeElement.setAttribute("min_pow", min_pow);


        foreach (Edge *edge, node->edges()) {
            if (edge->destNode()->number() == node->number()) // Пропускать входящие стрелки
                continue;
            
            QDomElement edgeElement = doc.createElement("edge");
            edgeElement.setAttribute("to", edge->destNode()->number());

            double r_r, r_x;
            edge->getResist(r_r, r_x);
            edgeElement.setAttribute("register_r", r_r);
            edgeElement.setAttribute("register_x", r_x);

            double pow_a, pow_r;
            edge->getPower(pow_a, pow_r);
            edgeElement.setAttribute("power_a", pow_a);
            edgeElement.setAttribute("power_r", pow_r);

            double otr_pow_a, pol_pow_a;
            edge->getLimitPower(otr_pow_a, pol_pow_a);
            edgeElement.setAttribute("otr_pow_a", otr_pow_a);
            edgeElement.setAttribute("pol_pow_a", pol_pow_a);

            edgeElement.setAttribute("number", edge->number());

            edgeElement.setAttribute("state", edge->getstate());
            nodeElement.appendChild(edgeElement);
        }
        root.appendChild(nodeElement);
    }

    stream << doc.toString();
    m_undoStack->setClean();
}

QString Document::fileName() const
{
    return m_fileName;
}

void Document::setFileName(const QString &fileName)
{
    m_fileName = fileName;
}

void Document::setBal(int number)
{
    m_balEdit->setText(QString::number(number));
    QPalette palette;

    if (m_bal) { // Сбросить выделение старого bal узла
        m_bal->setHighlighted(false);
        m_bal = false;
    }

    foreach (Node *node, nodes()) {
        if (node->number() == number) {
            m_bal = node;
            m_bal->setHighlighted(true);
            palette.setColor(QPalette::Base, Qt::white);
            m_balEdit->setPalette(palette);
            break;
        }
    }

    if (!m_bal) { // Несуществующий узел
        palette.setColor(QPalette::Base, Qt::yellow);
        m_balEdit->setPalette(palette);
    }

    updateCharacters();
}

int Document::bal() const
{
    return m_bal ? m_bal->number() : 0;
}

/*
     QMouseEvent хранит информацию об указателе мыши и статус кнопок
     Нажатие на одну из кнопок мыши
*/
void Document::mousePressEvent(QMouseEvent *event)          // QMouseEvent (хранит информацию об указателе мыши)
{
    if (qgraphicsitem_cast<QGraphicsProxyWidget*>(itemAt(event->pos()))) { // Обработка Bal
        QGraphicsView::mousePressEvent(event);
        return;
    }

    switch (MainWindow::instance()->editingMode()) {
    case MainWindow::Select:
    {
        // Костыль для невозможности выделения элементов разных типов
        QGraphicsItem *item = itemAt(event->pos());
        if (item && item->type() == Node::Type && !selectedEdges().isEmpty()) {
            scene()->clearSelection();
        }

        if (item && item->type() == Edge::Type && !selectedNodes().isEmpty()) {
            scene()->clearSelection();
        }

        QGraphicsView::mousePressEvent(event);
        QList<QGraphicsItem*> itemList = scene()->selectedItems();
        QList<Node*> nodeList;
        foreach (item, itemList) {
            if (item->type() == Node::Type) {
                m_oldPos = ((Node*)item)->pos();
                break;
            }
        }

        break;
    }
    
    case MainWindow::AddNode:
        break;
        
    case MainWindow::AddEdge:
    {
        m_tmpSourceNode = 0;
        QGraphicsItem *item = itemAt(event->pos());
        if (item && item->type() == Node::Type) {
            m_tmpSourceNode = qgraphicsitem_cast<Node*>(item);
            if (m_tmpSourceNode) {
                m_tmpSourceNode->setSelected(true);
            }
            m_tmpDestNode = 0;
        }
        break;
    }
    }
}

/*
    Отпускаем кнопку мыши
*/
void Document::mouseReleaseEvent(QMouseEvent *event)
{    
    if (qgraphicsitem_cast<QGraphicsProxyWidget*>(itemAt(event->pos()))) { // Обработка Bal
        QGraphicsView::mouseReleaseEvent(event);
        return;
    }

    switch (MainWindow::instance()->editingMode())
    {
    case MainWindow::Select:
    {
        QList<QGraphicsItem*> itemList = scene()->selectedItems();
        QList<Node*> nodeList;
        foreach (QGraphicsItem *item, itemList)
        {
            if (item->type() == Node::Type)
            {
                nodeList << (Node*)item;
            }
        }

        QGraphicsView::mouseReleaseEvent(event);
        if (!nodeList.isEmpty()) {
            switch(nodeList[0]->mode()) {
            case Node::Relax:
                break;
                
            case Node::Resize:
            {
                m_undoStack->push(new ResizeNodeCommand(this, nodeList, nodeList[0]->radius()));
                break;
            }

            case Node::Move:
            {
                QPointF diffPos = nodeList[0]->pos() - m_oldPos;
                if (diffPos != QPointF(0., 0.))
                {
                    m_undoStack->push(new MoveNodeCommand(this, nodeList, diffPos, true));
                }
                break;
            }
            }
        }
        break;
    }

    case MainWindow::AddNode:
    {
        Node *node = new Node();
        QPointF point = mapToScene(event->pos());
        m_undoStack->push(new AddNodeCommand(this, node));
        node->setPos(point);
        break;
    }
        
    case MainWindow::AddEdge:
        if (m_tmpSourceNode && m_tmpDestNode && (m_tmpSourceNode != m_tmpDestNode) &&
            !m_tmpSourceNode->linkedWith(m_tmpDestNode))
        {
            Edge *edge = new Edge(m_tmpSourceNode, m_tmpDestNode);
            m_undoStack->push(new AddEdgeCommand(this, edge));
        }
        break;
    }
}

/*
    Двигаем мышку, если одна из кнопок нажата
*/
void Document::mouseMoveEvent(QMouseEvent *event)
{
    if (event->buttons() == Qt::NoButton) {
        QGraphicsView::mouseMoveEvent(event);
        return;
    }
        
    switch (MainWindow::instance()->editingMode()) {
    case MainWindow::Select:
    {
        if (selectedNodes().isEmpty() || selectedNodes()[0]->mode() == Node::Relax)
            break;
        
        QList<QGraphicsItem*> itemList = scene()->selectedItems();
        QList<Node*> nodeList;
        foreach (QGraphicsItem *item, itemList)
        {
            if (item->type() == Node::Type) {
                nodeList << (Node*)item;
            }
        }

        if (!nodeList.isEmpty())
        {
            switch(nodeList[0]->mode()) {
            case Node::Relax:
                break;

            case Node::Resize:
            {
                m_undoStack->push(new ResizeNodeCommand(this, nodeList, nodeList[0]->radius()));
                break;
            }

            case Node::Move:
            {
                QPointF diffPos = nodeList[0]->pos() - m_oldPos;
                m_undoStack->push(new MoveNodeCommand(this, nodeList, diffPos));
                break;
            }
            }
        }
        break;
    }

    case MainWindow::AddNode:
        break;

    case MainWindow::AddEdge:
    {
        QGraphicsItem *item = itemAt(event->pos());
        if (m_tmpSourceNode && item && item->type() == Node::Type) {
            if (m_tmpDestNode)
                m_tmpDestNode->setSelected(false);
            m_tmpDestNode = qgraphicsitem_cast<Node*>(item);
            m_tmpDestNode->setSelected(true);
        }
        break;
    }
    }
    QGraphicsView::mouseMoveEvent(event);
}

void Document::resizeEvent(QResizeEvent *event)
{
    QGraphicsView::resizeEvent(event);
    if (m_proxyWidget) {
        scene()->setSceneRect(0., 0., event->size().width(), event->size().height());
        m_proxyWidget->setPos(scene()->sceneRect().bottomRight() - QPointF(m_proxyWidget->size().width(), m_proxyWidget->size().height()));
    }
}

bool Document::eventFilter(QObject *obj, QEvent *event)
{
    if (event->type() != QEvent::KeyPress || obj != m_balEdit) {
        return QGraphicsView::eventFilter(obj, event);
    }

    QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
    switch (keyEvent->key()) {
    case Qt::Key_Return:
    case Qt::Key_Enter:
    {
        QFocusEvent fe(QEvent::FocusOut, Qt::ActiveWindowFocusReason);
        QCoreApplication::sendEvent(m_balEdit, &fe);
        m_balEdit->clearFocus();
        return true;
    }

    default:
        return QGraphicsView::eventFilter(obj, event);
    }
}

void Document::balEditFinished()
{
    m_undoStack->push(new ChangeBalCommand(this, bal(), m_balEdit->text().toInt()));
}
